﻿/*
    Copyright (c) 2007 - 2009, Carlos Guzmán Álvarez

    All rights reserved.

    Redistribution and use in source and binary forms, with or without modification, 
    are permitted provided that the following conditions are met:

        * Redistributions of source code must retain the above copyright notice, 
          this list of conditions and the following disclaimer.
        * Redistributions in binary form must reproduce the above copyright notice, 
          this list of conditions and the following disclaimer in the documentation and/or 
          other materials provided with the distribution.
        * Neither the name of the author nor the names of its contributors may be used to endorse or 
          promote products derived from this software without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

using System;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using Framework.Net.Xmpp.Core;
using Framework.Net.Xmpp.Serialization.Extensions;
using Framework.Net.Xmpp.Serialization.InstantMessaging.Client;
using Framework.Net.Xmpp.Serialization.InstantMessaging.Client.Presence;

namespace Framework.Net.Xmpp.InstantMessaging
{
    /// <summary>
    /// Represents a contact resource
    /// </summary>
    public sealed class XmppContactResource : INotifyPropertyChanged
    {
        #region · INotifyPropertyChanged Members ·

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region · Fields ·

        private XmppSession                     session;
        private XmppContact                     contact;
        private XmppJid                         resourceId;
        private XmppContactPresence             presence;
        private XmppContactEntityCapabilities   capabilities;
        private string                          avatarHash;
        private string                          originalAvatarPath;
        private string                          onlineAvatarPath;

        #endregion

        #region · Properties ·

        /// <summary>
        /// Gets or sets the resource id.
        /// </summary>
        /// <value>The resource id.</value>
        public XmppJid ResourceId
        {
            get { return this.resourceId; }
        }

        /// <summary>
        /// Gets or sets the resource presence information.
        /// </summary>
        /// <value>The presence.</value>
        public XmppContactPresence Presence
        {
            get { return this.presence; }
        }

        /// <summary>
        /// Gets or sets the resource capabilities.
        /// </summary>
        /// <value>The capabilities.</value>
        public XmppContactEntityCapabilities Capabilities
        {
            get { return this.capabilities; }
            internal set { this.capabilities = value; }
        }

        /// <summary>
        /// Gets the avatar image file path
        /// </summary>
        public string OriginalAvatarPath
        {
            get { return this.originalAvatarPath; }
            private set
            {
                if (this.originalAvatarPath != value)
                {
                    this.originalAvatarPath = value;
                    this.NotifyPropertyChanged("OriginalAvatarPath");
                }
            }
        }

        /// <summary>
        /// Gets the avatar image file path
        /// </summary>
        public string OnlineAvatarPath
        {
            get { return this.onlineAvatarPath; }
            private set
            {
                if (this.onlineAvatarPath != value)
                {
                    this.onlineAvatarPath = value;
                    this.NotifyPropertyChanged("OnlineAvatarPath");
                }
            }
        }

        #endregion

        #region · Constructors ·

        /// <summary>
        /// Initializes a new instance of the <see cref="XmppContactResource"/> class.
        /// </summary>
        internal XmppContactResource(XmppSession session, XmppContact contact, XmppJid resourceId)
        {
            this.session    = session;
            this.contact    = contact;
            this.resourceId = resourceId;
            this.presence   = new XmppContactPresence(this.session);

            this.WireEvents();
        }

        #endregion

        #region · Methods ·

        public override string ToString()
        {
            return this.resourceId.ToString();
        }

        #endregion

        #region · Internal Methods ·

        internal void Update(Presence presence)
        {
            this.Presence.Update(presence);

            if (this.Presence.PresenceStatus == XmppPresenceState.Offline)
            {
                string cachedHash = this.session.Configuration.GetAvatarHash(this.ResourceId.BareIdentifier);

                // Grab stored images for offline users
                if (!String.IsNullOrEmpty(cachedHash))
                {
                    // Update the avatar hash and file Paths
                    this.avatarHash         = cachedHash;
                    this.OriginalAvatarPath = this.session.Configuration.GetOriginalAvatarPath(this.ResourceId.BareIdentifier);
                    this.OnlineAvatarPath   = this.session.Configuration.GetOnlineAvatarPath(this.ResourceId.BareIdentifier);
                }
            }

            foreach (object item in presence.Items)
            {
                if (item is Error)
                {
#warning TODO: Handle the error
                }
                else if (item is VCardAvatar)
                {
                    VCardAvatar vcard = (VCardAvatar)item;

                    if (vcard.Photo != null && vcard.Photo.Length > 0)
                    {
                        if (!String.IsNullOrEmpty(vcard.Photo))
                        {
                            // Check if we have the avatar cached
                            string cachedHash = this.session.Configuration.GetAvatarHash(this.ResourceId.BareIdentifier);

                            if (cachedHash == vcard.Photo)
                            {
                                // Update the avatar hash and file Paths
                                this.avatarHash         = vcard.Photo;
                                this.OriginalAvatarPath = this.session.Configuration.GetOriginalAvatarPath(this.ResourceId.BareIdentifier);
                                this.OnlineAvatarPath   = this.session.Configuration.GetOnlineAvatarPath(this.ResourceId.BareIdentifier);
                            }
                            else
                            {
                                // Update the avatar hash
                                this.avatarHash = vcard.Photo;

                                // Avatar is not cached request the new avatar information
                                this.RequestAvatar();
                            }
                        }
                    }
                }
                else if (item is EntityCapabilities)
                {                    
                    EntityCapabilities caps = (EntityCapabilities)item;

                    // Request capabilities only if they aren't cached yet for this resource
                    // or the verfiication string differs from the one that is cached
                    if (this.Capabilities == null || this.Capabilities.VerificationString != caps.VerificationString)
                    {
                        this.Capabilities = new XmppContactEntityCapabilities(this.session, this);

                        this.Capabilities.Node = caps.Node;
                        this.Capabilities.HashAlgorithmName = caps.HashAlgorithmName;
                        this.Capabilities.VerificationString = caps.VerificationString;

                        // Discover Entity Capabilities Extension Features
                        if (this.contact.Subscription == XmppContactSubscriptionType.Both ||
                            this.contact.Subscription == XmppContactSubscriptionType.To)
                        {
                            this.Capabilities.DiscoverCapabilities();
                        }
                    }
                }
            }
        }

        #endregion

        #region · Private Methods ·

        private void WireEvents()
        {
            this.session.StateChanged                       += new EventHandler<XmppSessionStateChangedEventArgs>(OnSessionStateChanged);
            this.session.Connection.VCardMessageReceived    += new EventHandler<XmppvCardMessageReceiveEventArgs>(OnVCardMessageReceived);
        }

        private void UnwireEvents()
        {
        }

        /// <summary>
        /// Requests the contact avatar.
        /// </summary>
        private void RequestAvatar()
        {
            if (this.contact.Subscription == XmppContactSubscriptionType.Both ||
                this.contact.Subscription == XmppContactSubscriptionType.To)
            {
                IQ iq = new IQ();

                iq.ID   = XmppIdentifierGenerator.Generate();
                iq.Type = IQType.Get;
                iq.To   = this.ResourceId.ToString();
                iq.From = this.session.UserId.ToString();

                iq.Items.Add(new VCard());

                this.session.Send(iq);
            }
        }

        private void NotifyPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion

        #region · Event Handlers ·

        private void OnSessionStateChanged(object sender, XmppSessionStateChangedEventArgs e)
        {
            if (e.NewState == XmppSessionState.LoggingOut)
            {
                this.UnwireEvents();
            }
        }

        private void OnVCardMessageReceived(object sender, XmppvCardMessageReceiveEventArgs e)
        {
            if (e.QueryResult.From == this.ResourceId.ToString())
            {
                // Update the Avatar image
                VCard vCard = (VCard)e.QueryResult.Items[0];

                if (vCard.Photo.Photo != null && vCard.Photo.Photo.Length > 0)
                {
                    Image onlineAvatar = null;
                    Image originalAvatar = null;

                    try
                    {
                        this.OriginalAvatarPath = null;
                        this.OnlineAvatarPath = null;

                        using (MemoryStream avatarStream = new MemoryStream(vCard.Photo.Photo))
                        {
                            originalAvatar = Image.FromStream(avatarStream);
                            onlineAvatar = ImageHelper.Resize(originalAvatar, new Size(48, 48));

                            this.session.Configuration.SaveAvatar(this.ResourceId.BareIdentifier, this.avatarHash, originalAvatar, onlineAvatar);
                        }

                        this.OriginalAvatarPath = this.session.Configuration.GetOriginalAvatarPath(this.ResourceId.BareIdentifier);
                        this.OnlineAvatarPath   = this.session.Configuration.GetOnlineAvatarPath(this.ResourceId.BareIdentifier);
                    }
                    catch
                    {
#warning TODO: Handle the exception
                    }
                    finally
                    {
                        if (originalAvatar != null)
                        {
                            originalAvatar.Dispose();
                            originalAvatar = null;
                        }
                        if (onlineAvatar != null)
                        {
                            onlineAvatar.Dispose();
                            onlineAvatar = null;
                        }
                    }
                }
                else
                {
                    // this.session.Configuration.SaveAvatar(this.ResourceId.BareIdentifier, this.avatarHash, null, null);
                }

                this.session.Configuration.Save();
            }
        }

        #endregion
    }
}
